global class util { 
	// Class variables can be used for communication between triggers 
	public static Boolean inQuoteTrigger = false;  
	public static Set<Id> opportunityIds = new Set<Id>(); 

	/* This class method returns the primary quotes for all opportunities 
	   associated with the given quotes, other than the quote passed into the method. 
	   If there is no other primary quote, the method returns null. */
	public static Quote__c[] getOtherPrimaryQuotes(list<Quote__c > qs) { 
		
		//Retrieve the Set of all opportunities referenced by the given quotes 
		Set<Id> ops = new Set<Id>(); 
		for(Quote__c q : qs) 
			ops.add(q.opportunity__c); 

		/* Now retrieve all quotes that reference the opportunities, 
		   excluding the given quotes */
		Quote__c[] others = [select id from quote__c where opportunity__c in :ops and id not in :qs and primary__c = true]; 
		       				 
		return others; 
	} 

	// This method moves opportunity line items to the given quote q. 
	public static void copyLineItemsFromOpp(List<Quote__c> qs) { 
		/* We will modify and update the quotes with totals at the end, so we clone it 
		   first since Trigger.new records cannot be modified in after triggers. Notice that 
		   we use the deepClone method on the list as clone for sobjects creates a shallow 
		   copy which is still read-only.  */
		List<Quote__c> quoteList = qs.deepClone(true); 
		
		try { 
			/* By setting the inQuoteTrigger class variable, this method tells 
			   the quoteLine trigger that it should disable itself during this 
			   operation (the quoteLine trigger is described in Example 3)  */
			inQuoteTrigger = true; 

			//Now the method creates an empty array of quote line items 
			List<QuoteLine__c> quoteItemList = new List<QuoteLine__c>(); 
			
			/* Next get the opportunity line items for the respective Opportunities. 
			   This demonstrates the aggregate relationship query pattern for getting 
			   children correlated back to their parent. You could also query 
			   OpportunityLineItem directly but would then have to correlate the items 
			   back to their respective parents (opportunity) inside the code manually. 
			   We are constructing a map so the respective opportunity 
			   (with its line items) can be easily correlated back to quote as we process 
			   them below. */
			Map<Id, Opportunity> opportunityMap = new Map<Id, Opportunity>([select Id, (select pricebookentry.product2id, color__c, unitprice, quantity, totalprice from opportunitylineitems order by createddate) from Opportunity where Id IN :getOpportunityIds(quoteList)]); 

			// Iterate through the quotes in the bulk operation. 
			for(Quote__c quote:quoteList) { 
				
				/* Next, the method manually calculates the overall total for the quote 
				   by iterating over an array of OpportunityLineItem records from the 
				   preceeding SOQL query. The query selects the data from opportunity 
				   line items that match the opportunity associated with the quote. */			   
				Double tot = 0; // The running total 
				Integer i = 0; // Number the line items 
				
				// Get the respective opportunity and its line items from the map. 
				Opportunity opp = opportunityMap.get(quote.opportunity__c); 
	
				// If this quote is not associated to an opportunity then skip it. 
				if(opp == null) continue; 
				
				for (opportunitylineitem oppline : opp.OpportunityLineItems) { 
					i++; 
	
					/* For each opportunity line item, the method 
					   calculates the running total */
					if (oppline.unitprice != null && oppline.quantity != null) 
						tot += oppline.unitprice * oppline.quantity; 
	
					/* And then it adds a new quote line item to the quoteitems, 
					 array, initializing it with values. */
					quoteItemList.add(new quoteline__c( 
					quote__c = quote.id, 
	
					/* The method uses the counter variable to number 
					   the line items. Because the SOQL query ordered 
					   them by the date they were created with an ORDER BY 
					   clause, they will be numbered that way as well. */
					name = String.valueOf(i), 
	
					/* The method then uses the values for product, unit 
					   price, quantity, total price, and color from the 
					   opportunity line item. */
					product__c = oppline.pricebookentry.product2id,
					unitprice__c = oppline.unitprice, 
					quantity__c = oppline.quantity, 
					totalprice__c = oppline.totalprice, 
					color__c = oppline.color__c)); 
				} 

				// The method sets the total for this quote. 
				quote.total__c = tot; 
			} 
		
			/* The method adds the whole array of quote line items 
			   to the database with a single insert statement. */
			insert quoteItemList; 
			update quoteList; 
		
		} finally { 

			// To clean up the working variable, the method resets inQuoteTrigger to false; 
			inQuoteTrigger = false; 
		} 
	} 
	
	/* Generate a unique set of opportunity Ids that represents all the 
	   quotes in the argument list. */
	private static Set<Id> getOpportunityIds(List<Quote__c> quotes) { 
		Set<Id> idSet = new Set<Id>(); 
		for(Quote__c quote:quotes) { 
			if(quote.opportunity__c != null) idSet.add(quote.opportunity__c); 
		} 
		return idSet; 
	} 
	
	/* Set the colors on the line items equals to the master product catalog 
	   colors, which may have changed */
	static void updateColors(Id qId) { 
		// First load the line items into memory 
		QuoteLine__c[] lineItems = [select product__c from quoteLine__c where quote__c = :qId]; 
		// Now find the distinct product2 Ids 
		Set<Id> productIds = new Set<Id>(); 
		
		for (QuoteLine__c line : lineItems) productIds.add(line.product__c); 

		/* Now query all of those products at once. Id is always selected implicitly. 
		   Note the use of IN  */
		Map<Id, Product2> products = new Map<Id, Product2>([select color__c from product2 where id in :productIds]); 
		
		// Update the line items, using the Map we created above 
		QuoteLine__c[] lineItemsToUpdate = new QuoteLine__c[0]; 
		
		for (QuoteLine__c line : lineItems) { 
			Product2 product = products.get(line.product__c); 
			if (product != null && line.color__c != product.color__c) { 
				line.color__c = product.color__c; 
				lineItemsToUpdate.add(line); 
			} 
		} 
		
		update lineItemsToUpdate; 
	} 

	webService static Proposal__c makeProposal(String id) { 
		Proposal__c p = new Proposal__c(name = 'propose it', quote__c = id); 
		insert p; 
		return p; 
	} 
	
	static testMethod void testQuoteApp() { 
		/* To begin, the sample application first creates the necessary records 
		   to test OpportunityLineItems: Pricebook2, Product2, and PricebookEntry 
		   First it creates a product with a standard price */
		Product2 p = new product2(name='x', color__c='c'); 
		insert p; 
		Pricebook2 stdPb = [select Id from Pricebook2 where isStandard=true limit 1]; 
		insert new PricebookEntry(pricebook2id = stdPb.id, product2id = p.id, unitprice=1.0, isActive=true); 
		
		// Next, it creates a new pricebook with an entry for this product 
		Pricebook2 pb = new pricebook2(name='test'); 
		insert pb; 
		PricebookEntry pbe = new PricebookEntry(pricebook2id=pb.id, product2id=p.id, unitprice=1.0, isActive=true); 
		insert pbe; 

		/* To test the first example from the sample application, the test 
		   method creates an opportunity line item using the pricebook entry, 
		   and then asserts that the Color field was correctly copied from the 
		   product after record insertion. */
		Opportunity o = new Opportunity(name='test', pricebook2id=pb.id, 
		stageName='Open', CloseDate=Date.newInstance(2006,10,10)); 
		insert o; 
		OpportunityLineItem oli = new OpportunityLineItem(opportunityid=o.id, pricebookentryid=pbe.id, unitprice=1.5, quantity=2); 
		insert oli; 
		System.assertEquals('c', [select color__c from opportunitylineitem where id = :oli.id].color__c); 

		/* To test the fifth example from the sample application, the test method 
		   creates a primary quote and then asserts that its Primary field cannot 
		   be deselected. The code uses the try...catch testing idiom to assert 
		   that the error message is correct. */
		Quote__c q = new Quote__c(opportunity__c = o.id, primary__c = true); 
		insert q; 

		// Cannot mark it non-primary 
		q.primary__c = false; 
		try { 
			update q; 
		} catch (System.DmlException e) { 
			System.assert(e.getMessage().contains('first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, Primary quote cannot be marked non-primary: [Primary__c]'), e.getMessage()); 
			System.assertEquals('Primary quote cannot be marked non-primary', e.getDmlMessage(0)); 
			System.assertEquals('Primary__c', e.getDmlFields(0)[0]); 
		} 
	
		/* To test the second and third examples from the sample application, the 
		   test method asserts that the inserted quote has the proper quote line 
		   data copied from the opportunity line item, and that the quote's total 
		   value is properly calculated. */
		System.assertEquals(1, [select count() from quoteline__c where quote__c = :q.id]); 

		// Total on the quote should be correct 
		System.assertEquals(3.0, [select total__c from quote__c where id = :q.id].total__c); 

		/* To test the fourth example from the sample application, the test 
		   method asserts that the opportunity and quote cannot be deleted 
		   since the quote is primary */
		try { 
			delete o; 
			System.assert(false); 
		} catch (System.DmlException e) { 
			System.assert(e.getDmlMessage(0).indexOf('Cannot delete opportunity with a quote') > -1); 
		} 
		
		try { 
			delete q; 
			System.assert(false); 
		} catch (System.DmlException e) { 
			System.assert(e.getDmlMessage(0).indexOf('Primary quote cannot be deleted') > -1); 
		} 

		/* The test method now creates a second quote, marks it primary, and 
		   verifies that the first quote is no longer primary. */
		Quote__c q2 = new Quote__c(opportunity__c = o.id, primary__c = true); 
		insert q2; 
		System.assert(![select primary__c from quote__c where id = :q.id].primary__c); 

		/* Can't have the same opportunity marked as primary for the same opportunity 
		   in the same insert/update */
		Quote__c[] quotes = new Quote__c[] { new Quote__c(opportunity__c = o.id, primary__c = true), new Quote__c(opportunity__c = o.id, primary__c = true)} ; 
		
		try { 
			insert quotes; 
			System.assert(false); 
		} catch (System.DmlException e) { 
			System.assert(e.getDmlMessage(0).indexOf('More than one quote is attempting to be primary for this opportunity: ' + o.id) > -1); 
		} 

		/* Finally, the test method performs DML operations on quote line 
		   items to assert that the quote line item values are properly 
		   recalculated when associated values change. */
		QuoteLine__c qLine = new QuoteLine__c(quote__c = q.id, product__c = p.id, unitprice__c = 2.0, quantity__c = 2); 
		QuoteLine__c qLine1 = qLine.clone(); 
		insert qLine; 
		System.assertEquals(7.0, [select total__c from quote__c where id = :q.id].total__c); 
		
		qLine.unitprice__c = 3.0; 
		update qLine; 
		
		System.assertEquals(9.0, [select total__c from quote__c where id = :q.id].total__c); 
		
		delete qLine; 
		System.assertEquals(3.0, [select total__c from quote__c where id = :q.id].total__c); 

		// Bulk line item operation 
		insert new QuoteLine__C[]{qLine1, qLine1.clone()}; 
		System.assertEquals(11.0, [select total__c from quote__c where id = :q.id].total__c); 
		
		// Test that updating the product master color and then updating the quote colors works 
		p.color__c = 'c2'; 
		update p; 
		updateColors(q.id); 
		for (QuoteLine__c line : [select color__c from quoteLine__c where quote__c = :q.id]) { 
			System.assertEquals('c2', line.color__c); 
		} 
	} 
}